package com.example.demo54acl.config;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.domain.*;
import org.springframework.security.acls.jdbc.BasicLookupStrategy;
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
import org.springframework.security.acls.jdbc.LookupStrategy;
import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.AclService;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import javax.sql.DataSource;

/**
 * @EnableGlobalMethodSecurity 注解的配置表示开启项目中 @PreAuthorize、@PostAuthorize 以及 @Secured 注解的使用
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class AclConfig {

    /**
     * 由于引入了数据库的一整套东西，并且配置了数据库连接信息，所以这里可以注入 DataSource 实例以备后续使用。
     */
    @Autowired
    DataSource dataSource;

    /**
     * AclAuthorizationStrategy 实例用来判断当前的认证主体是否有修改 Acl 的权限，准确来说是三种权限：修改 Acl 的 owner；修改 Acl 的审计信息以及修改 ACE 本身。这个接口只有一个实现类就是 AclAuthorizationStrategyImpl，我们在创建实例时，可以传入三个参数，分别对应了这三种权限，也可以传入一个参数，表示这一个角色可以干三件事。
     */
    @Bean
    AclAuthorizationStrategy aclAuthorizationStrategy(){
        return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_admin"));
    }

    /**
     * PermissionGrantingStrategy 接口提供了一个 isGranted 方法，这个方法就是最终真正进行权限比对的方法，该接口只有一个实现类 DefaultPermissionGrantingStrategy，直接 new 就行了。
     */
    @Bean
    PermissionGrantingStrategy permissionGrantingStrategy(){
        return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
    }

    /**
     * 在 ACL 体系中，由于权限比对总是要查询数据库，造成了性能问题，因此引入了 Ehcache 做缓存。AclCache 共有两个实现类：SpringCacheBasedAclCache 和 EhCacheBasedAclCache。我们前面已经引入了 ehcache 实例，所以这里配置 EhCacheBasedAclCache 实例即可。
     */
    @Bean
    AclCache aclCache(){
        return new SpringCacheBasedAclCache(new ConcurrentMapCache("aclCache"),permissionGrantingStrategy(),aclAuthorizationStrategy());
    }

    /**
     * LookupStrategy 可以通过 ObjectIdentity 解析出对应的 Acl。LookupStrategy 只有一个实现类就是 BasicLookupStrategy，直接 new 即可。
     */
    @Bean
    LookupStrategy lookupStrategy(){
        return new BasicLookupStrategy(dataSource,aclCache(),aclAuthorizationStrategy(),permissionGrantingStrategy());
    }

    /**
     * AclService 接口中主要定义了一些解析 Acl 对象的方法，通过 ObjectIdentity 对象解析出其对应的 Acl。
     * AclService 主要有两类实现接口：JdbcAclServiceJdbc MutableAclService
     * 前者主要是针对 Acl 的查询操作，后者支持 Acl 的添加、更新以及删除等操作。我们常用的是 JdbcMutableAclService。
     */
    @Bean
    JdbcMutableAclService jdbcMutableAclService(){
        JdbcMutableAclService jdbcMutableAclService = new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
        // 参考 https://blog.csdn.net/bulargy/article/details/83234296
        jdbcMutableAclService.setClassIdentityQuery("SELECT LAST_INSERT_ID()");
        jdbcMutableAclService.setClassIdentityQuery("SELECT LAST_INSERT_ID()");
        return jdbcMutableAclService;
    }

    /**
     * PermissionEvaluator 是为表达式 hasPermission 提供支持的。由于本案例后面使用类似于 @PreAuthorize("hasPermission(#noticeMessage, 'WRITE')") 这样的注解进行权限控制，因此之类需要配置一个 PermissionEvaluator 实例。
     */
    @Bean
    PermissionEvaluator permissionEvaluator(){
        return new AclPermissionEvaluator(jdbcMutableAclService());
    }

}
